﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml.Linq;
using Windows.Data.Xml.Dom;
using Windows.Data.Xml.Xsl;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Media;

namespace Ebt.Converters
{
    /// <summary>
    /// Converts "zeros" and "non zeros" to <see cref="Visibility"/>. Use <c>ConverterParameter</c> to invert the behavior.
    /// </summary>
    public class VisibilityConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, string language)
        {
            bool isVisible;

            string strValue;

            if (value == null)
                isVisible = false;
            else if (value is bool)
                isVisible = (bool)value;
            else if ((strValue = value as string) != null)
                isVisible = !string.IsNullOrEmpty(strValue);
            else if (Equals(value, Visibility.Collapsed))
                isVisible = false;
            else if (Equals(value, 0))
                isVisible = false;
            else
                isVisible = true;

            var invert = parameter != null;
            if (invert)
                isVisible = !isVisible;

            return isVisible ? Visibility.Visible : Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            var isVisible = (Visibility)value == Visibility.Visible;
            var invert = parameter != null;
            if (invert)
                isVisible = !isVisible;

            switch (targetType.Name)
            {
                case "Visibility": return isVisible ? Visibility.Visible : Visibility.Collapsed;
                default: return isVisible;
            }
        }

        #endregion
    }

    /// <summary>
    /// Converts "zeros" and "non zeros" to boolean. Use <c>ConverterParameter</c> to invert the behavior.
    /// </summary>
    public class BooleanConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, string language)
        {
            bool positive;

            string strValue;

            if (value == null)
                positive = false;
            else if (value is bool)
                positive = (bool)value;
            else if ((strValue = value as string) != null)
                positive = !string.IsNullOrEmpty(strValue);
            else if (Equals(value, Visibility.Collapsed))
                positive = false;
            else if (Equals(value, 0))
                positive = false;
            else
                positive = true;

            var invert = parameter != null;
            if (invert)
                positive = !positive;

            return positive;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            var positive = (bool)value;
            var invert = parameter != null;
            if (invert)
                positive = !positive;

            switch (targetType.Name)
            {
                case "Visibility": return positive ? Visibility.Visible : Visibility.Collapsed;
                case "Boolean": return positive;
                default:
                    throw new NotSupportedException("Conversion from Boolean to " + targetType.Name + " is not supported.");
            }
        }

        #endregion
    }

    public class NegativeConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, string language)
        {
            return !(bool)value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            return !(bool)value;
        }

        #endregion
    }

    public class LowerCaseConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, string language)
        {
            return value != null ? value.ToString().ToLower() : string.Empty;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotSupportedException();
        }

        #endregion
    }

    /// <summary>
    /// Converts <see cref="DateTime"/> to local time string. <c>ConverterParameter</c> must contain a format string.
    /// </summary>
    public class DateTimeConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value == null || !(value is DateTime))
                return string.Empty;

            var date = (DateTime)value;
            var format = parameter as string;

            if (format == null)
                return string.Empty;

            date = date.ToLocalTime();

            return date.ToString(format);
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotSupportedException();
        }

        #endregion
    }

    /// <summary>
    /// Converts <see cref="DateTime"/> to string. <c>ConverterParameter</c> must contain a format string.
    /// </summary>
    public class SimpleDateTimeConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value == null || !(value is DateTime))
                return string.Empty;

            var date = (DateTime)value;
            var format = parameter as string;

            if (format == null)
                return string.Empty;

            return date.ToString(format);
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotSupportedException();
        }

        #endregion
    }

    /// <summary>
    /// Converts an object to <c>String</c> using the specified format. <c>ConverterParameter</c> must contain a format string.
    /// </summary>
    public class StringFormatConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value == null)
                return string.Empty;

            var format = parameter as string;

            if (format == null)
                return string.Empty;

            return string.Format("{0:" + format + "}", value);
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotSupportedException();
        }

        #endregion
    }

    public class EnumToInt32Converter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, string language)
        {
            return (int)value;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            return Enum.ToObject(targetType, value);
        }

        #endregion
    }

    /// <summary>
    /// Converts a value to <see cref="Visibility"/>. <c>ConverterParameter</c> must contain a second value for comparision. If the value and parameter are equals then gives <c>Visible</c>.
    /// </summary>
    public class ConstantToVisibilityConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value is Enum)
            {
                parameter = Enum.ToObject(value.GetType(), parameter);
            }
            return Equals(value, parameter) ? Visibility.Visible : Visibility.Collapsed;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotSupportedException("ConvertBack is not supported");
        }

        #endregion
    }

    /// <summary>
    /// Converts a list of <see cref="Point"/>s to the <see cref="PointCollection"/>.
    /// </summary>
    public class PointCollectionConverter : IValueConverter
    {
        #region IValueConverter Members

        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value == null)
                return null;

            var points = (IEnumerable<Point>)value;
            var pointCollection = new PointCollection();
            foreach (var point in points)
                pointCollection.Add(point);
            return pointCollection;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotSupportedException("ConvertBack is not supported");
        }

        #endregion
    }

    /// <summary>
    /// Multiplies a value by the parameter.
    /// </summary>
    public class MultiplyingConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            double a = (double)System.Convert.ChangeType(value, typeof(double), CultureInfo.InvariantCulture);
            double b = parameter == null ? 1d : (double)System.Convert.ChangeType(parameter, typeof(double), CultureInfo.InvariantCulture);

            return a * b;
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotSupportedException();
        }
    }

    /// <summary>
    /// Evaluates an expression against the value. The expression string is passed through the parameter and can contain some string and numeric XSLT functions.
    /// </summary>
    public class EvaluatorConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            string expression = parameter as string ?? string.Empty;
            string variable = (value ?? string.Empty).ToString();
            expression = expression.Replace("$1", variable);

            return System.Convert.ChangeType(Evaluate(expression), targetType, CultureInfo.InvariantCulture);
        }

        public virtual object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotSupportedException();
        }

        protected static string Evaluate(string expression)
        {
            var xsl = string.Format("<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>" +
                                    "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">" +
                                    "<xsl:template match=\"/\">" +
                                    "<result>" +
                                    "<xsl:value-of select=\"{0}\" />" +
                                    "</result>" +
                                    "</xsl:template>" +
                                    "</xsl:stylesheet>",
                                    new System.Text.RegularExpressions.Regex(@"([\+\-\*])")
                                        .Replace(expression, " ${1} ")
                                        .Replace("/", " div ")
                                        .Replace("%", " mod "));
            var doc = new XmlDocument();
            doc.LoadXml(xsl);

            var xslt = new XsltProcessor(doc);
            var xDoc = new XmlDocument();
            xDoc.LoadXml("<r/>");
            var r = xslt.TransformToString(xDoc);

            var d = XDocument.Parse(r);
            var result = d.Element("result").Value;

            return result;
        }
    }
}
